<?php
/** 
 * CTG_DBLib
 * PHP数据库操作类 
 * @author			 	CnTaiGe
 * @license 			http://www.apache.org/licenses/LICENSE-2.0
 * @link			     	http://www.cntaige.com
 * @version			    Version 0.1
 * @copyright		    Copyright (c)  2014  CnTaiGe Inc.
 */
class CTG_DBLib {
	protected $pdo; // 实例
	protected $statement; // 获取的statement
	protected $sql; // sql语句
	protected $whereStr = ''; // 组装where条件
	protected $valuesArr = array (); // 组装要查询的值
	protected $table; // 操作的数据表
	protected $selectStr = 'SELECT * FROM '; // select 语句
	protected $limit = ''; // limit参数
	protected $order = ''; // 排序
	protected $config = array (
			'HOST' => 'localhost', // 数据库地址
			'PORT' => 3306, // 数据库端口
			'CHARSET' => 'utf8', // 数据库编码
			'DB_NAME' => '', // 数据库名
			'DB_USER' => 'root', // 用户名
			'DB_PWD' => '', // 密码
			'TABLE' => ''  // 默认操作的数据表
		); // 配置
	/**
	 * 初始化实例并配置pdo
	 *
	 * @access public
	 * @param array $config
	 *        	配置参数
	 * @throws Exception 抛出查询错误异常
	 */
	public function __construct($config = array()) {
		if (! class_exists ( 'PDO' )) {
			throw new Exception ( '没有找到PDO这个类，请配置相关项！' );
		}
		$this->config = array_merge ( $this->config, $config );
		$this->table = $this->config ['TABLE'];
		try {
			$this->pdo = new PDO ( 'mysql:host=' . $this->config ['HOST'] . ';dbname=' . $this->config ['DB_NAME'] . ';port=' . $this->config ['PORT'], $this->config ['DB_USER'], $this->config ['DB_PWD'], array (
					PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
					PDO::MYSQL_ATTR_INIT_COMMAND => 'set names ' . $this->config ['CHARSET'] 
			) );
			$this->pdo->setAttribute ( PDO::ATTR_EMULATE_PREPARES, false );
		} catch ( PDOException $p ) {
			throw new Exception ( $p->getMessage () );
		}
	}
	/**
	 * 设置数据表
	 *
	 * @param string $table
	 *        	要操作的数据表
	 * @return CTG_DBLib 当前对象
	 */
	public function table($table) {
		$this->table = $table;
		return $this;
	}
	/**
	 * 统计数量
	 *
	 * @param array $where        	
	 * @return int 数量
	 */
	public function count($where = array()) {
		if (! empty ( $where )) {
			$this->where ( $where );
		}
		$this->selectStr = 'SELECT COUNT(*) AS _count FROM ';
		$this->limit = ' LIMIT 1 ';
		$re = $this->find ();
		return $re [0] ['_count'];
	}
	/**
	 * 是否存在条件查询内容
	 *
	 * @param array $where        	
	 * @return boolean
	 */
	public function has($where = array()) {
		if (! empty ( $where ))
			$this->where ( $where );
		elseif (empty ( $this->whereStr ))
			return false;
		$this->sql = $this->selectStr . $this->table . ' WHERE ' . trim ( $this->whereStr, ' AND ' ) . ' LIMIT 1'; // 组装sql查询
		$row = $this->prExRo ();
		return $row ? true : false;
	}
	/**
	 * 设定数据结果集记录数
	 *
	 * @param integer $start
	 *        	开始行数， $end 结束行数，
	 *        	只设置第一个参数时为返回最大的记录行数
	 * @return CTG_DBLib
	 */
	public function limit($start, $end = '') {
		$this->limit = ' LIMIT ' . (( int ) $start) . (empty ( $end ) ? '' : ',' . ( int ) $end);
		return $this;
	}
	/**
	 * 数据集排序
	 *
	 * @param string $order
	 *        	排序字段
	 * @return CTG_DBLib
	 */
	public function order($order) {
		$this->order = ' ORDER BY ' . $order . ' ';
		return $this;
	}
	/**
	 * 更新数据
	 *
	 * @access public
	 * @param array $data
	 *        	要更新的数据 字段=>值
	 * @return string number
	 */
	public function update($data) {
		$keys = array_keys ( $data );
		$this->valuesArr = array_merge ( array_values ( $data ), $this->valuesArr );
		$kstr = implode ( ',', array_map ( function ($v) {
			return '`' . $v . '` = ? ';
		}, $keys ) );
		$this->sql = 'UPDATE ' . $this->table . ' SET ' . $kstr . ' WHERE ' . trim ( $this->whereStr, ' AND ' );
		return $this->prExRo ();
	}
	/**
	 * 删除数据，
	 * 如果不传入查询条件，将不进行任何查询操作
	 *
	 * @param array $where
	 *        	删除条件 字段=>值
	 * @return number 影响的行数
	 */
	public function delete($where = array()) {
		if (! empty ( $where )) {
			$this->where ( $where );
		}
		if (empty ( $this->valuesArr ))
			return 0;
		$this->sql = 'DELETE FROM ' . $this->table . ' WHERE ' . $this->whereStr;
		return $this->prExRo ();
	}
	/**
	 * 插入数据
	 *
	 * @param array $data
	 *        	字段=>值
	 * @return string 返回插入的主键id
	 */
	public function insert($data) {
		$key = array_keys ( $data );
		$this->valuesArr = array_values ( $data );
		$kstr = implode ( ',', array_map ( function ($v) {
			return '`' . $v . '`';
		}, $key ) );
		$vstr = implode ( ',', array_fill ( 0, count ( $key ), '?' ) );
		$vstr = ' VALUES ( ' . $vstr . ' ) ';
		$kstr = ' ( ' . $kstr . ' )';
		$this->sql = 'INSERT INTO ' . $this->table . $kstr . $vstr;
		$this->prExRo ();
		return $this->pdo->lastInsertId ();
	}
	private function arrToStr($arr, $ks, &$arrV) {
		// 数组组装字符串
		$kstr = '';
		foreach ( $arr as $k => $v ) {
			$k = strtoupper ( $k );
			$flip = array_flip ( $v );
			if (is_int ( end ( $flip ) ))
				continue;
			$kstr .= '(' . implode ( ' ' . $k . ' ', array_map ( function ($c) {
				// 组装字段加=？用于预处理
				return (strrpos ( $c, '.' ) || strrpos ( $c, '(' )) ? ' ' . $c . '= ? ' : ' `' . $c . '` = ? ';
			}, $flip ) ) . ') ' . $ks . ' ';
			foreach ( $v as $vv ) {
				$arrV [] = $vv;
			}
		}
		return trim ( $kstr, ' ' . $ks . ' ' ) . ' AND ';
	}
	private function arrayW($arr) {
		// 计算数组维度
		$c = 0;
		if (is_array ( $arr )) {
			$c ++;
			foreach ( $arr as $v ) {
				$c += self::arrayW ( $v );
				break;
			}
		}
		return $c;
	}
	/**
	 * 查询的条件
	 *
	 * @param array $where
	 *        	字段=>值,
	 *        	like查询:'like'=>array(字段=>值),
	 *        	关联查询:'&'=>array('table1.id','table2.uid')
	 * @return CTG_DBLib 当前对象
	 */
	public function where($where) {
		$this->whereStr = '';
		$kstrt = '';
		$arrV = array ();
		if (self::arrayW ( $where ) == 1) {
			foreach ( $where as $k => $v ) {
				$kstrt = (strrpos ( $k, '.' ) || strrpos ( $k, '(' )) ? ' ' . $k . '= ? ' : '( `' . $k . '` = ? )';
				$arrV [] = $v;
			}
		} else {
			foreach ( $where as $ks => $vs ) {
				if (strtoupper ( $ks ) == 'LIKE') {
					// like查询
					foreach ( $vs as $lk => $lv ) {
						$kstrt .= '(' . $lk . ' LIKE ?' . ') AND ';
						$arrV [] = $lv;
					}
					continue;
				} elseif ($ks == '&') {
					// 关联查询
					$kstrt .= '(' . implode ( '=', $vs ) . ') AND ';
					continue;
				}
				$kstrt .= self::arrToStr ( self::arrayW ( $where ) == 2 ? $where : $vs, strtoupper ( $ks ), $arrV );
			}
		}
		$this->whereStr .= $kstrt;
		$this->valuesArr = $arrV;
		return $this;
	}
	/**
	 * 要查询的字段
	 *
	 * @param array $fields
	 *        	array(字段=>别名,字段1,字段2...)
	 * @return CTG_DBLib
	 */
	public function field($fields = array()) {
		$fieldArr = array ();
		foreach ( $fields as $k => $v ) {
			$fieldArr [] = (is_int ( $k ) ? '' : ((strrpos ( $k, '.' ) || strrpos ( $k, '(' )) ? $k . ' AS ' : '`' . $k . '` AS ')) . ((strrpos ( $v, '.' ) || strrpos ( $v, '(' )) ? $v : '`' . $v . '`');
		}
		$fieldStr = implode ( ',', $fieldArr );
		$this->selectStr = 'SELECT ' . (empty ( $fieldStr ) ? '*' : $fieldStr) . ' FROM ';
		return $this;
	}
	
	/**
	 * 查询数据结果集
	 *
	 * @return array 返回关联数组结果集
	 */
	public function find() {
		$this->sql = $this->selectStr . $this->table . (empty ( $this->whereStr ) ? '' : (' WHERE ' . trim ( $this->whereStr, ' AND ' ))) . $this->order . $this->limit; // 组装sql查询
		$this->prExRo ();
		$this->statement->setFetchMode ( PDO::FETCH_ASSOC );
		$resultArr = $this->statement->fetchAll ();
		$this->selectStr = 'SELECT * FROM ';
		
		return $resultArr;
	}
	/**
	 * 预处理操作获取pdoStatemnet对象
	 *
	 * @return number 返回影响的行数
	 */
	protected function prExRo() {
		$this->statement = $this->pdo->prepare ( $this->sql );
		$this->statement->execute ( $this->valuesArr );
		return $this->statement->rowCount ();
	}
	/**
	 * 安全预处理查询
	 *
	 * @param string $sql
	 *        	要查询的语句
	 * @param array $values
	 *        	要查询对应的?值
	 * @return Ambigous <Ambigous, unknown, number>|Ambigous <unknown, number>
	 *         查询数据时返回结果集，更新添加删除数据时返回影响的行数
	 */
	public function rawQuery($sql, $values = array()) {
		if (empty ( $values )) {
			return self::query ( $sql );
		}
		$this->statement = $this->pdo->prepare ( $sql );
		$this->statement->execute ( $values );
		$this->statement->setFetchMode ( PDO::FETCH_ASSOC );
		$resultArr = $this->statement->fetchAll ();
		return empty ( $resultArr ) ? $this->statement->rowCount () : $resultArr;
	}
	/**
	 * 直接执行sql语句，此方法未做防注入安全处理
	 *
	 * @param string $sql
	 *        	要执行的sql语句
	 * @return Ambigous <array, number>
	 *         查询数据时返回结果集，更新添加删除数据时返回影响的行数
	 */
	public function query($sql) {
		$this->sql = $sql;
		$this->statement = $this->pdo->query ( $sql );
		$this->statement->setFetchMode ( PDO::FETCH_ASSOC );
		$row = $this->statement->rowCount ();
		$arr = $this->statement->fetchAll ();
		return empty ( $arr ) ? $row : $arr;
	}
	/**
	 * pdo内置转义处理
	 *
	 * @param string $string        	
	 * @param int $type        	
	 * @return string 处理后的字符
	 */
	public function quote($string, $type = PDO::PARAM_STR) {
		return $this->pdo->quote ( $string, $type );
	}
	/**
	 * 获取最后一次完整的查询sql语句
	 *
	 * @return string
	 */
	public function querySql() {
		$vas = array ();
		foreach ( $this->valuesArr as $v ) {
			$vas [] = $this->pdo->quote ( $v ); // 组装值到预处理的sql语句
		}
		return vsprintf ( str_replace ( '?', '%s', $this->sql ), $vas );
	}
	/**
	 * 获取预处理前的查询语句
	 *
	 * @return string
	 */
	public function preSqlStr() {
		return $this->sql;
	}
}
